/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the ViewMyCurrency extension.
 *
 * The Initial Developer of the Original Code is
 * Will Moffat.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//dump("// $Id: WILL_OptionsDialogManager.js,v 1.52 2007/03/19 12:55:27 Will Exp $;\n");

VMC.optionsManager = null;

VMC.doToggleAdvancedOptions = function() {   // this is global in case the Manager object fails to initalise (due to a bug)
  var advancedBox=VMC.ge('WILL-advanced-options');    
  if (advancedBox.hidden) {
    // make the advanced options appear
    VMC.doToggleAdvancedOptions._lastSelectedTab=VMC.ge('WILL-options-tabbox').selectedTab;
    advancedBox.hidden = false;
    VMC.ge('WILL-options-tabbox').selectedTab=advancedBox;
  } else {
    // make the advanced options dissapear
    advancedBox.hidden = true;
    //Go back to last tab
    VMC.ge('WILL-options-tabbox').selectedTab=VMC.doToggleAdvancedOptions._lastSelectedTab;
  }
};



VMC.OptionsDialogLoad = function() {

  VMC.doToggleAdvancedOptions._lastSelectedTab = VMC.ge('WILL-options-tabbox').selectedTab;

  // we could be called from Extensions Options or directly from my own gui. So can't rely on window.arugments

  //WILL: TODO: fix nasty hack! There must be a better way to do this?
  var vmcGui=VMC.getBrowserWindow().VMC.gui;
  if (!vmcGui._confirmInstalled()) {
    alert('ViewMyCurrency options not available until setup wizard completed\nRunning setup wizard...');
    window.close();
    vmcGui._considerDoingInstall();
    return;
  }

  VMC.optionsManager = new VMC.OptionsDialogManager();
};



VMC.Preference = function( guiElementId, readValueFunc, writeValueFunc, getValueFromGuiFunc, setGuiValueFunc, updateFromGuiFunc) {

  var originalValue = readValueFunc();
  var guiElement    = VMC.ge(guiElementId);
  setGuiValueFunc(guiElement,originalValue);
  guiElement.addEventListener('command',function() {VMC.optionsManager.doUpdateExampleText.call(VMC.optionsManager);}, false); //WILL: TODO: I don't really understand the use capture argument. //Use call to allow use of 'this'
  this.updateBasedOnGui = function() {
    var val=getValueFromGuiFunc(guiElement);
    updateFromGuiFunc(val);
  };
  
  this.writeIfChanged = function() {
    var currentValue = getValueFromGuiFunc(guiElement);
    if (originalValue != currentValue) {
      VMC.log('DBG','Pref '+guiElementId+' has changed. Writing new value');
      writeValueFunc(currentValue);
      return true;
    } else {
      VMC.log('DBG','Pref '+guiElementId+' not changed');
      return false;
    }
  };
};


VMC.PreferenceList = function() {
  var items=[];

  function apply(func) {
    for (var i=0;i<items.length;i++) {
      func(items[i]);
    }
  }
	   
  this.add = function(pref) { items.push(pref); };
 
 this.allUpdateBasedOnGui = function() { 
    apply( function(pref) { pref.updateBasedOnGui(); } );
  };

  this.allWriteIfChanged = function() {
    apply( function(pref) { pref.writeIfChanged(); } );
  };
};



//WILL: these comments are out of date!
// this works without seeing the main ViewMyCurrency object - it just gets/sets the prefs
// this requires ViewMyCurrency to use watchers to detect and act on the changes
// Advantages: PrefManager doesn't depend on ViewMyCurrency
//             Prefs can be safely edited in about:config 
//             Everything stays in sync if a new application window is started (with Control-N for example)
VMC.OptionsDialogManager = function() {
  var self = this;

  this.prefManager       = new VMC.PrefManager().init(); //two-stage init required for Firefox 1.0
  
  this.sourceCurrencyList = new VMC.CurrencyList();
  this.targetCurrencyList = new VMC.CurrencyList();
  this.prefManager.readSourceCurrencyList(this.sourceCurrencyList);
  this.prefManager.readTargetCurrencyList(this.targetCurrencyList);

  this.exchangeRateCache = new VMC.ExchangeRateCache( this.prefManager.readRate, this.prefManager.writeRate,
						       this.sourceCurrencyList,this.targetCurrencyList );
  
  this._init = _init;
  //functions
  this.doButtonModifyTargetList = doButtonModifyTargetList;
  this.doButtonModifySourceList = doButtonModifySourceList;
  this.doButtonOK               = doButtonOK;
  this.doButtonResetAll         = doButtonResetAll;
  this.doButtonDeletePrefs      = doButtonDeletePrefs;
  this.doUpdateExampleText      = doUpdateExampleText;

  this._webUpdater         = this.exchangeRateCache.webUpdater;

  this.mouseClick          = mouseClick;
  this.doButtonUpdate        = doButtonUpdate;
  this.doLinkQuoteSource   = doLinkQuoteSource;
  this.doLinkWikipedia   = doLinkWikipedia;

  this.observe             = observe;
  this.die                 = die;

  this._initPreferencesGui = _initPreferencesGui;
  this._initWebServiceSelect = _initWebServiceSelect;

  this._initTable        = _initTable;
  this._setTextBoxes     = _setTextBoxes;
  this._mouseClickOnCell = _mouseClickOnCell;

  this._lastClickedRate  = null;

  this._updateIfListModified = _updateIfListModified;
  this._sourceListDirty = false;
  this._targetListDirty = false;

  this._setExampleSourceAndTargetCurrencies = _setExampleSourceAndTargetCurrencies;

  this._init();
  this._initTable();

  this.exchangeRateCache.webUpdater.addObserver(this);

  if (!this.prefManager) {VMC.alert('this window cannot be run standalone - needs a prefManager');  return; }

  document.documentElement.getButton("accept").focus();

  function _init() {
    this._completeList = VMC.currencyDB.getCompleteList();

    var advancedBox=VMC.ge('WILL-advanced-options');
    if (this.prefManager.getDebugLevel()) {
      advancedBox.hidden=false;
    } else {
      advancedBox.hidden=true;
    }






    var display=VMC.ge('WILLstatusbar-display');
    display.value='(Automatically updates every '+this.exchangeRateCache.UPDATE_INTERVAL+' hours)';

    this._initPreferencesGui();
    this._initWebServiceSelect();
  }

  function _initWebServiceSelect() {
    this._quoteSourceRow=VMC.ge('WILL-quoteSource-row');

    ////////////////////////////////
    // helper to  create the GUI: //
    ////////////////////////////////

    var menulist;
    var menupopup;
    //WILL: TODO: bug in XUL? menulist.appendItem(webservice.getName()) };

    var addMenuItem = function(name) {
      var item = document.createElement('menuitem');
      item.setAttribute('label',name);
      item.setAttribute('value',name);
      menupopup.appendChild(item);
    }

    var fillMenupopup = function(webservice) {
      var label=webservice.getName();
      addMenuItem(label);
    };
      

    var createMenuList = function(rateObj) {	
      if (!rateObj.menulist) { //it's possible we already created a menulist if the user changes the currency lists
	menupopup = document.createElement('menupopup');
	menulist = document.createElement('menulist');
	menulist.hidden=true;
	menulist.setAttribute('id','AUTO-'+rateObj+'-menulist');
	if (!rateObj.webService) { 
	  addMenuItem('None','tooll');
	} else {
	  VMC.webServiceList.applyFunc(fillMenupopup);
	}
	menulist.appendChild(menupopup);
	self._quoteSourceRow.appendChild(menulist);
	rateObj.menulist=menulist; // store a reference to this menu in the rateObj itself
      }
      menulist = rateObj.menulist;
      if (rateObj.webService) {
	menulist.setAttribute('value',rateObj.webService.getName()); // select the currently used webservice
      } else {
	menulist.setAttribute('value','None');
	menulist.setAttribute('tooltiptext','Calculated from: '+rateObj.getDependentCurrency() ); 
      }
    };

    ///////////////////////////
    // helper for the  prefs //
    ///////////////////////////

    var makePref = function(rateObj) {
      if (!rateObj.guiPref && !rateObj.isCalculated) { // WILL: TODO: allow users to change a rate from isCalculated to updated from web
	var prefWebService = new  VMC.Preference(
  	 'AUTO-'+rateObj+'-menulist',                                                            // guiElement
 	 function()       { self.prefManager.readRate(rateObj); return rateObj.webService.getName(); }, // readValueFunc //WILL: side-effect - updates the rateObj
 	 function(val)    { rateObj.webService=VMC.webServiceList.getServiceFromName(val); self.prefManager.writeRate(rateObj);   }, // writeValueFunc(val) 
  	 function(element)     { return element.getAttribute('value');  },    // getValueFromGuiFunc
  	 function(element,val) { element.value=val;                },    // setGuiValueFunc
  	 function(val) { rateObj.webService = VMC.webServiceList.getServiceFromName(val); } // updateFromGuiFunc //WILL: should be different rom writeValueFunc
  		  );
	self._prefList.add(prefWebService);
	rateObj.guiPref=prefWebService; // remember that we created this pref
      } // else do nothing
     };

    ////////////////////////
    // invoke the helpers //
    ////////////////////////
    
    if (this.exchangeRateCache._getNumRates() ) {
      this.exchangeRateCache._applyToEach(createMenuList);
      this.exchangeRateCache._applyToEach(makePref);
    } else {
      VMC.log('WARN','you need to add from and to currencies!');
      
    }

     

  }

  function doButtonOK() {
    if (this._sourceListDirty) { this.prefManager.setSourceCurrencyList(this.sourceCurrencyList); }
    if (this._targetListDirty) { this.prefManager.setTargetCurrencyList(this.targetCurrencyList); }

    this._prefList.allWriteIfChanged();
    
    window.close();
  }

  //WILL: buttonCancel is handled directly by the XUL (more robust, you should always be able to close!)

  function doButtonResetAll() {
    if (this.prefManager.deleteViewMyCurrencyPrefs()) {
      this.prefManager.storeInitialPrefs(); //reset all the prefs
      this._init();
    }
  }

  function doButtonDeletePrefs() {
    if (this.prefManager.deleteViewMyCurrencyPrefs('\nFirefox will quit!')) {
      // the user really did it - shock horror!
      VMC.getBrowserWindow().goQuitApplication();
    }
  }



  // returnVal comes from the Currency Chooser Dialog
  function _updateIfListModified(returnVal,listName) {
    var newList=returnVal['newSelectedCurrencyList'];

    if (newList) { //WILL: only using this as a boolean! Not as the CurrencyList because it's just the one we passed in!

      //this.tree.hidden=true; //WILL: TODO: stop the giving updte errors (it's items have changed)

      if      (listName=="target") { this._targetListDirty=true; }
      else if (listName=="source") { this._sourceListDirty=true; }
      else { VMC.log('ERR','Unknown list '+listName); }

      var allRatesAvailable=this.exchangeRateCache.sourceOrTargetListChanged();
      var isInvalid = this.exchangeRateCache._getNumRates() ? 'false' : 'true';
      document.documentElement.getButton("accept").setAttribute('disabled',isInvalid); // disable OK button if there are no exchange rates

      this._initTable(); //  update the tree view
      this._initWebServiceSelect(); // update the webservice menulists and prefs
      if (!allRatesAvailable) { 
	this._numRatesToUpdate=this.exchangeRateCache.updateFromWeb(false); //don't force update for items which are not old
      }
    }

  }

  function doButtonModifyTargetList() {
    var returnVal={};
    var resetFunc    = this.prefManager.getDefaultTargetListPref;
    window.openDialog('chrome://viewmycurrency/content/WILL_TargetDialog.xul', 'VMC.TargetDialog', 'chrome,resizable,modal',
		      this.targetCurrencyList,this._completeList,resetFunc,returnVal);
    this._updateIfListModified(returnVal,"target");
  }

  function doButtonModifySourceList() {
    var returnVal={};
    var resetFunc    = this.prefManager.getDefaultSourceListPref;
    window.openDialog('chrome://viewmycurrency/content/WILL_SourceDialog.xul', 'VMC.SourceDialog', 'chrome,resizable,modal',
		      this.sourceCurrencyList,this._completeList,resetFunc,returnVal);
    this._updateIfListModified(returnVal,"source");
  }    

  function _initPreferencesGui() {
    this._prefList = new VMC.PreferenceList();

    // if you add a new preference here then you might have to add it
    // to interestingKeys and prefChangedCallBack in WILL_ViewMyCurrency.js for updates to take place

    // just creating the pref initalises it's value from PrefManager and updates the gui
    var prefForceSpace = new  VMC.Preference(
		  'WILL-forceSpace-checkbox', // guiElement
		  function()       { return self.prefManager.getForceSpace(); },    // readValueFunc
		  function(val)    { self.prefManager.setForceSpace(val);     },    // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val) { VMC.NumberConversion.prototype.forceSpaceBetweenNumberAndSymbol = val; } // updateFromGuiFunc
		  );
    this._prefList.add(prefForceSpace);

    var prefHideInStatusbar = new  VMC.Preference(
		  'WILL-hideInStatusbar-checkbox', // guiElement
		  function()       { return self.prefManager.getHideInStatusbar(); },    // readValueFunc
		  function(val)    { self.prefManager.setHideInStatusbar(val);     },    // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val)    { VMC.ge('WILL-hideInStatusbarWarning').hidden=!val; } // updateFromGuiFunc
		  );
    this._prefList.add(prefHideInStatusbar);

    var prefHighlightChanges = new  VMC.Preference(
		  'WILL-highlightChanges-checkbox', // guiElement
		  function()       { return self.prefManager.getHighlightChanges(); },    // readValueFunc
		  function(val)    { self.prefManager.setHighlightChanges(val);     },    // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val)    { VMC.setOutline(VMC.ge('WILL-visualExample-label'),val ? 'solid 2px red' : '') } // updateFromGuiFunc
		  );
    this._prefList.add(prefHighlightChanges);

    var prefTooltipMode = new  VMC.Preference(
		  'WILL-tooltipMode-checkbox', // guiElement
		  function()       { return self.prefManager.getTooltipMode(); },    // readValueFunc
		  function(val)    { self.prefManager.setTooltipMode(val);     },    // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val)    { /* no visual change to example */        }     // updateFromGuiFunc
		  );
    this._prefList.add(prefTooltipMode);



    var separatorWarning = function() {
      var style='';
      if (VMC.Number.prototype.decimalSeparator == VMC.Number.prototype.thousandsSeparator) { style='color:red'; }
      VMC.ge('WILL-format-box').setAttribute('style',style);
    };
    var prefDecimalSeparator = new  VMC.Preference(
		  'WILL-decimal-list', // guiElement
		  function()       { return self.prefManager.getDecimalSeparator(); },    // readValueFunc
		  function(val)    { self.prefManager.setDecimalSeparator(val);     },    // writeValueFunc(val)
		  function(element)     { return element.value;             },    // getValueFromGuiFunc
		  function(element,val) { element.value=val;               },    // setGuiValueFunc
		  function(val) { VMC.Number.prototype.decimalSeparator = val; separatorWarning(); } // updateFromGuiFunc
		  );
    this._prefList.add(prefDecimalSeparator);

    var prefThousandsSeparator = new  VMC.Preference(
		  'WILL-thousands-list', // guiElement
		  function()       { return self.prefManager.getThousandsSeparator(); },    // readValueFunc
		  function(val)    { self.prefManager.setThousandsSeparator(val);     },    // writeValueFunc(val)
		  function(element)     { return element.value;             },    // getValueFromGuiFunc
		  function(element,val) { element.value=val;                },    // setGuiValueFunc
		  function(val) { VMC.Number.prototype.thousandsSeparator = val; separatorWarning(); } // updateFromGuiFunc
		  );
    this._prefList.add(prefThousandsSeparator);


    var prefHideOriginal = new VMC.Preference(
	          'WILL-hideOriginal-checkbox',
		  function()       { return self.prefManager.getHideOriginalCurrency(); },    // readValueFunc
		  function(val)    { self.prefManager.setHideOriginalCurrency(val);     },    // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val) { self._hideOriginal = val; } // updateFromGuiFunc
		  );
    this._prefList.add(prefHideOriginal);
					       
    var prefRoundNumbers = new VMC.Preference(
	          'WILL-roundNunbers-checkbox',
		  function()       { return self.prefManager.getRoundNumbers(); },  // readValueFunc
		  function(val)    { self.prefManager.setRoundNumbers(val);     },  // writeValueFunc(val)
		  function(element)     { return element.checked;             },    // getValueFromGuiFunc
		  function(element,val) { element.checked=val;                },    // setGuiValueFunc
		  function(val) { VMC.Number.prototype.performRounding = val; }     // updateFromGuiFunc
		  );
    this._prefList.add(prefRoundNumbers);



    var selectRadioButton = function(radiogroup,value) {
      value=value.toString(); // gui values must always be strings
      var nodes=radiogroup.childNodes;
      for (var i=0;i<nodes.length;i++) {
	if (nodes[i].value == value) { radiogroup.selectedIndex=i; }
      }
    };
    var prefForceSymbolPosition = new VMC.Preference(
	          'WILL-order-radiogroup',
		  function()       { return self.prefManager.getForceSymbolPos(); },    // readValueFunc
		  function(val)    { self.prefManager.setForceSymbolPos(val);     },    // writeValueFunc(val)
		  function(element)     { return element.value;             },    // getValueFromGuiFunc
		  selectRadioButton,                                             // setGuiValueFunc
		  function(val) { VMC.NumberConversion.prototype.forceSymbolPosition = val; } // updateFromGuiFunc
		  );
    this._prefList.add(prefForceSymbolPosition);


    var prefLayoutOrder = new VMC.Preference(
	          'WILL-layoutOrder-radiogroup',
		  function()       { return self.prefManager.getLayoutOrder(); },   // readValueFunc
		  function(val)    { self.prefManager.setLayoutOrder(val);     },   // writeValueFunc(val)
		  function(element)     { return (element.value=='true');      },   // getValueFromGuiFunc
		  selectRadioButton, //reuse from forceSymbolPosition               // setGuiValueFunc
		  function(val) { VMC.NumberConversion.prototype.targetInBrackets = val; } // updateFromGuiFunc
		  );
    this._prefList.add(prefLayoutOrder);


    var prefDebugLevel = new  VMC.Preference(
		  'tb_debugLevel', // guiElement
		  function()       { return self.prefManager.getDebugLevel(); },    // readValueFunc
		  function(val)    { self.prefManager.setDebugLevel(val);     },    // writeValueFunc(val)
		  function(element)     { return element.value;             },    // getValueFromGuiFunc
		  function(element,val) { element.value=val;                },    // setGuiValueFunc
		  function(val)         { VMC.debugLevel = val;                }  // updateFromGuiFunc
		  );
    this._prefList.add(prefDebugLevel);

    this.doUpdateExampleText(); //initalise the example number
  }


  //WILL: TODO: don't need to create a new currency each time if user doesn't have their own
  function _setExampleSourceAndTargetCurrencies() {

    var targetCurrencyList=this.targetCurrencyList;
    var sourceCurrencyList=this.sourceCurrencyList;

    var sourceCurrency;
    var targetCurrency;
    
    
    //WILL: This goes through every combination and selects the last valid one.
    //      I wanted to use a break to stop on first match but labels are not carried into closures
    var setSourceDifferentToTarget = function(s) { if (!targetCurrency.equals(s)) {sourceCurrency=s;} };
    var setTargetAndCheckSource    = function(t) { targetCurrency=t; sourceCurrencyList._applyFunc(setSourceDifferentToTarget); };
    targetCurrencyList._applyFunc(setTargetAndCheckSource);

    if (!targetCurrency || !sourceCurrency) { 
      // we seem to be missing currencies so use the defaults for the example
      var USD=VMC.currencyDB.createListFromCountry('United States').items[0];
      sourceCurrencyList=new VMC.CurrencyList();
      sourceCurrencyList.add(USD);
      targetCurrency = VMC.currencyDB.createListFromCountry('Eurozone').items[0]; //WILL: TODO: this explicit reference isn't nice
      sourceCurrency = USD;
    }

    this._exampleTargetCurrency = targetCurrency;
    this._exampleSourceCurrency = sourceCurrency;
    this._exampleSourceCurrencyList = sourceCurrencyList;
    this._exampleConversionProfile = new VMC.ConversionProfile( VMC.currencyDB.generateDefaultSymbolTableFor(sourceCurrencyList) );
  }

  function doUpdateExampleText() {

    var exampleText='Fixme!';

    this._setExampleSourceAndTargetCurrencies();

    var origCurrSymbol=this._exampleSourceCurrency.getHtml();
    var nbsp='\u00A0';
    var origText='Hamster soup costs '+origCurrSymbol + nbsp+ '1000000 and the profit is 10.23'+origCurrSymbol;
    var getRateFunc = function() { return 1; }; // fake rate to demonstrate point! //WILL: TODO: use real rate if available
    
    var tooltiptext='Converting "'+origText+'" without exchange rate';
    VMC.ge('WILL-visualExample-label').setAttribute('tooltiptext',tooltiptext);

    this._prefList.allUpdateBasedOnGui(); 
    
    exampleText = VMC.NumberConversion.prototype.currencyConvertStr(origText, this._exampleTargetCurrency, this._exampleConversionProfile,
					  getRateFunc, this._hideOriginal, null /*defaultSourceCurr*/);
    if (exampleText===null || exampleText==origText) { VMC.log('WARN','text should have been converted '+origText); exampleText='Fixme!'; }
    
    VMC.ge('WILL-visualExample-label').firstChild.nodeValue=exampleText;

  }

  function die() {
    this.exchangeRateCache.webUpdater.removeObserver(this);
  }

  function _initTable() {

    var targets=this.targetCurrencyList.items;

    var treecols=VMC.ge('VMC.treecols');

    VMC.killChildren(treecols);

    if (!targets || !targets.length) { VMC.log('WARN','table is empty!'); return; }
    
    var createCol = function(id,label) {
      var tc=document.createElement('treecol');
      tc.setAttribute('label',label);
      tc.setAttribute('id',   id);
      if (id) {	 tc.setAttribute('flex', '1'); }
      else {	 tc.setAttribute('flex', '2'); } //give more space to the exchange rate cols
      treecols.appendChild(tc);
    };
    

    createCol('VMC.colSource',null); //no heading text
    for (var i=0; i<targets.length; i++) {
      createCol(targets[i].code,targets[i].toString()); //id, label
    }

    this.tree=VMC.ge('exchangeRates-tree');

    this.tree.view  = new VMC.ExchangeRatesTreeView(this.exchangeRateCache);

    var s=this.tree.view.selection;
    s.select = s.adjustSelection = s.selectAll = s.rangedSelect = s.toggleSelect = function() {};
    this.tree.view.selection=s;

    this.tree.addEventListener('click',function(e) { VMC.optionsManager.mouseClick(e); }, false);

    this._setTextBoxes( {'rate_tb':'', 'status_tb':'', 'update_tb':'', 'err_tb':''} );

  }

  function doButtonUpdate() {
    this._numRatesToUpdate=this.exchangeRateCache.updateFromWeb(true); //force update
    //WILL: TODO: should turn into a cancel button during the update
    // this.observe() will be called automatically by the WebUpdater
  }


  function doLinkWikipedia() {  
    var url=VMC.ge('currWiki_tb').value;
    if (url) {
      VMC.getBrowserWindow().open(url);
    }
  }

  function doLinkQuoteSource() {  
    if (this._lastClickedRate) {
      if (this._lastClickedRate.isCalculated) { return; } //calculated rates don't have a webService
      var url=this._lastClickedRate.getUpdateUrl();
      VMC.getBrowserWindow().open(url);
    }
  }

  function _setTextBoxes(text) {
    var tb;
    for (box in text) {
      tb = VMC.ge(box);
      tb.setAttribute('style','color:black;'); // don't use default disabled grey for text
      tb.value=text[box];
    }

  }

  // only used here (to reduce event processing) (and to replay click)
  this.lastRowNum=null;
  this.lastColObj=null;

  function mouseClick(event)  {
    var rowObjHash = {}, colObjHash = {}, partObjHash = {};

    //    this.tree.view.selection.clearSelection();

    var treebox = this.tree.boxObject;
    treebox.QueryInterface(Components.interfaces.nsITreeBoxObject);
    treebox.getCellAt(event.clientX, event.clientY, rowObjHash, colObjHash, partObjHash);

    var colObj = colObjHash.value;
    var rowNum = rowObjHash.value;

    if (this.lastRowNum==rowNum && this.lastColObj==colObj) { return; } //we already got this

    this._mouseClickOnCell(rowNum,colObj);
  }

  function _mouseClickOnCell(rowNum,colObj) { 
    var treebox = this.tree.boxObject;
    this.tree.view.performActionOnCell("WILLselect",rowNum,colObj);

    if (this.lastRowNum && this.lastColObj) {
       treebox.invalidateCell(this.lastRowNum,this.lastColObj); //undraw the old cell (doesn't happen on the first click)
    }
    treebox.invalidateCell(rowNum,colObj); //draw the new selection

    this.lastRowNum=rowNum;
    this.lastColObj=colObj;
    

    if (!colObj) { return; }//WILL: when does this happen?
    if (rowNum<0 || rowNum>=this.sourceCurrencyList.items.length) { return; }

    //left header column    
    //OR we don't bother with Exchange Rate objects for the obvious!
    
    var colName=VMC.toColName(colObj);
    var showCurrencyDetails=false;
    if (colName=="VMC.colSource") { showCurrencyDetails=true; }
    else {
      var targetCurr=this.targetCurrencyList.findFromCode(colName);
      var sourceCurr=this.sourceCurrencyList.items[rowNum];
      if (sourceCurr.equals(targetCurr)) { showCurrencyDetails=true; }
    }

    var detailDeck=VMC.ge('WILL-showDetails-deck');
    var text = {};

    if (showCurrencyDetails) {
      detailDeck.selectedIndex=0;
      var currency=this.sourceCurrencyList.items[rowNum];
      text['currCode_tb']=currency.code;
      text['currName_tb']=currency.description;
      text['currLand_tb']=currency.country;
      text['currSymb_tb']=currency.getHtml();
      text['currForm_tb']=currency.formula;
      text['currWiki_tb']='http://en.wikipedia.org/wiki/List_of_circulating_currencies'; //WILL: TODO: add better link
      
    } else {
      detailDeck.selectedIndex=1;

      var exRate = this.exchangeRateCache.getRateObj(sourceCurr,targetCurr);

      var lastUpdate = exRate.getLastGoodUpdateTime();

      var textClass = (exRate.isCalculated) ? '' : 'text-link';
      VMC.ge('WILL-quoteSource-link').setAttribute('class',textClass);

      text['rate_tb']   = exRate.toString();
      text['status_tb'] = exRate.status;

      if (this._lastClickedRate) {this._lastClickedRate.menulist.hidden=true; }
      exRate.menulist.hidden=false;
      
      //W: TODO: this._quoteSourceMenu.setAttribute('tooltiptext','Select the web site that provides this exchange rate');
      //W: TODO: this._quoteSourceMenu.setAttribute('tooltiptext','No webservice supports this currency');

      text['update_tb'] = lastUpdate || "Never updated";
      var err=exRate.lastUpdateErr;
      var errRow=VMC.ge('WILL-error-row');
      if (err) {
	text['err_tb']    = err;
	errRow.hidden=false;
      } else {
	errRow.hidden=true;
      }
      this._lastClickedRate=exRate;	
    } // end if not showCurrencyDetails
    this._setTextBoxes( text );
  }

  // observe an update in progress
  function observe(status) {
    var progressBar=VMC.ge('VMC.updateProgress');
    var display=VMC.ge('WILLstatusbar-display');

    this.exchangeRateCache.setAllCalculatedRates(); // update any dependant rates as well

    this._mouseClickOnCell(this.lastRowNum,this.lastColObj); //replay the last mouse click to update the details box. WILL: TODO: more elegant solution?
    var treebox = this.tree.boxObject;
    treebox.invalidate(); //redraw the entire tree

    if (status.substr(0,3)=="END") {
      //we are finished
      progressBar.value=100;
      window.setTimeout( function() { 
			   progressBar.value=0;
			   treebox.invalidate();
			 } , 500); //redraw the tree after half a sec, WILL: why did I need this delay?
      display.value='Update complete';
    } else {
      var percentDone = 1 - this._webUpdater.fractionLeft();
      progressBar.value=percentDone*100;
      display.value='Updating '+this._webUpdater.numItemsToDo()+' exchange rates ...';
    }
  }


  function buttonDone() {
    if (!this.prefManager) {      VMC.alert('Could not save list - no main window');      return;    }

    //WILL TODO: say updating
    this.prefManager.setSourceCurrencyList(this.sourceList);

    //WILL: TODO: add watchers to do the update this.prefManager.exchangeRates.update();
    window.close();
  }
};


VMC.toColName = function(colObj) {
  if (!colObj) { return undefined; } // WILL: who does this?
  var colName;
  if (typeof colObj == "string") { //FF1 legacy code??
    colName=colObj;
  } else { // it's a treeColumn object
    colName=colObj.id;
  }
  if (!colName) { VMC.log('ERR','invalid column'+colObj); }
  return colName;
};

VMC.ExchangeRatesTreeView = function(exchangeRateCache) {
   var aserv=Components.classes["@mozilla.org/atom-service;1"].getService(Components.interfaces.nsIAtomService);
   this._exchangeRateCache=exchangeRateCache;
   if (!VMC.ExchangeRateCache.isValid(exchangeRateCache,true)) {
     VMC.log('WARN','invalid exchangeRateCache');
     return;
   }

   this._sourceList=this._exchangeRateCache.sourceCurrencyList;
   this._targetList=this._exchangeRateCache.targetCurrencyList;

   this._willSelected=[null,null]; //the currently selected cell [row,col]
   this.rowCount = this._sourceList.items.length;

   this._findRateObj = function(row,colName) {
    if (row<0 || row>=this._sourceList.items.length) { return null; }//WILL: log?
    if (colName=='VMC.colSource') { return null; }
    var toCurrency=this._targetList.findFromCode(colName,true); //failSilently
    if (!toCurrency) { return 'ERR'; } //WILL: TODO: why does this happen when I delte something from the list?
    var fromCurrency=this._sourceList.items[row];
    var rate=this._exchangeRateCache.getRateObj(fromCurrency,toCurrency);
    return rate;
   };

  this.getCellText = function(row,colObj){
    var colName=VMC.toColName(colObj);
    if (colName=='VMC.colSource') {
      if (this._sourceList.items && this._sourceList.items[row]) { return this._sourceList.items[row].toString(); }
      return 'ERR';
    }
    var rateObj = this._findRateObj(row,colName);
    if (!rateObj)   { return '';  }
    if (rateObj==1) { return '1'; }
    if (rateObj=='ERR') { return 'ERR'; }
    return rateObj.rate;
  };
  this.setTree = function(treebox){ this.treebox = treebox; };
  this.isEditable = function(index, colObj) { alert('h'); return false; };
  this.isContainer = function(row){ return false; };
  this.isSeparator = function(row){ return false; };
  this.isSorted = function(row){ return false; };
  this.getLevel = function(row){ return 0; };
  this.getImageSrc = function(row,colObj){ return null; };
  this.getRowProperties = function(row,props){};
  this.getCellProperties = function(row,colObj,props){
    var colName=VMC.toColName(colObj);
    if (this._willSelected[0]==row && this._willSelected[1]==colName) { props.AppendElement(aserv.getAtom("VMC-selected")); }
    var rateObj = this._findRateObj(row,colName);
    if (!rateObj || rateObj==1 || rateObj=='ERR') { return; }
    if (rateObj.isStatusInvalid() )  { props.AppendElement(aserv.getAtom("VMC-rateInvalid"));      }
    if (rateObj.isStatusOld()     )  { props.AppendElement(aserv.getAtom("VMC-rateOld"));          }
    if (rateObj.lastUpdateErr     )  { props.AppendElement(aserv.getAtom("VMC-rateDownloadErr"));  }
  };

  this.getColumnProperties = function(colid,colObj,props){};
  this.getParentIndex = function(row) {return -1; };
  this.cycleHeader = function(colObj, elem) { };
  this.performAction = function(action) {
    alert('WILL: got action '+action);
    //assume update - I'm too lazy to calculate which row changed
    this.treebox.beginUpdateBatch();
    this.rowCount = this._sourceList.items.length;
    this.treebox.endUpdateBatch();
  };
  this.performActionOnCell = function(action, row, colObj ) {
    var colName=VMC.toColName(colObj);
    this._willSelected=[row,colName];
  };
};
